package daobeauty.interceptor.invoke.impl;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;
import org.hibernate.ScrollableResults;
import org.hibernate.SessionFactory;

import daobeauty.dao.Limiting;
import daobeauty.dao.Sorting;
import daobeauty.interceptor.invoke.AbstractCurrentSessionInvokeStrategy;
import daobeauty.interceptor.invoke.ArgumentMask;
import daobeauty.interceptor.invoke.fill.EmptyFill;
import daobeauty.interceptor.invoke.fill.ParameterFillStrategy;
import daobeauty.interceptor.invoke.fill.PlainQueryFill;
import daobeauty.interceptor.invoke.parser.Parser;
import daobeauty.interceptor.invoke.parser.operator.Operator;
import daobeauty.util.ArrayListStack;
import daobeauty.util.EmptyStack;
import daobeauty.util.MethodSignatureUtils;
import daobeauty.util.Stack;

public class FindExistsByParsingInvoke extends AbstractCurrentSessionInvokeStrategy {
	private static final Log LOG = LogFactory.getLog(FindExistsByParsingInvoke.class);
	private final ArgumentMask argumentMask;
	private final ParameterFillStrategy fill;
	private final Operator operator;
	private final ArgumentStackStrategy stackStrategy;
	
	@SuppressWarnings("unchecked")
	public FindExistsByParsingInvoke(Method method, SessionFactory sessionFactory, Class domainClass, Set<String> domainProperties) {
		super(sessionFactory, domainClass);
		if (!Boolean.TYPE.isAssignableFrom(method.getReturnType()) && !Boolean.class.isAssignableFrom(method.getReturnType())){
			throw new IllegalArgumentException("FindExistsByParsing strategy only supports return type boolean, found return type "+method.getReturnType()+" for method "+MethodSignatureUtils.toMethodSignature(method));
		}
		argumentMask = new ArgumentMask(method, 0);
		if (argumentMask.hasLimiting()){
			throw new IllegalArgumentException("method "+MethodSignatureUtils.toMethodSignature(method)+" may not have a parameter of type "+Limiting.class.getName());
		}
		if (argumentMask.hasSorting()){
			throw new IllegalArgumentException("method "+MethodSignatureUtils.toMethodSignature(method)+" may not have a parameter of type "+Sorting.class.getName());
		}
		if (argumentMask.getFilteredArgumentsSize()<=0){
			fill = EmptyFill.INSTANCE;
			stackStrategy = new EmptyStackStrategy();
		}
		else{
			fill = new PlainQueryFill();
			stackStrategy = new ArrayListStackStrategy();
		}
		final String methodNameWithoutPrefix = method.getName().substring(12); // remove "findExistsBy"
		operator = new Parser(methodNameWithoutPrefix, domainProperties, argumentMask.getFilteredParameterTypes()).parse();
		if (LOG.isInfoEnabled()){
			LOG.info("created findExistsBy strategy with parsing for method "+MethodSignatureUtils.toMethodSignature(method));
			LOG.info("Query with "+operator.toTotalString());
		}
		if (operator.getTotalAmountArguments()!=argumentMask.getFilteredArgumentsSize()){
			LOG.warn("Amount of needed parameters for query ("+operator.getTotalAmountArguments()+") and defined parameters in method ("+argumentMask.getFilteredArgumentsSize()+") don't match.");	
		}
	}
	
	@SuppressWarnings("unchecked")
	public Object invoke(String methodName, Object[] args) throws Exception {
		final ArrayList usedArguments = new ArrayList((args==null?0:args.length));
		// TODO statt query vielleicht mit criteria?
		final Query q = getCurrentSession().createQuery("SELECT 1 FROM "+getDomainClass().getName()+" d WHERE "+operator.toHqlQuery(stackStrategy.getArgumentStack(argumentMask.getFiltered(args)), usedArguments));
		fill.fillParameters(q, usedArguments.toArray(new Object[usedArguments.size()]));
		q.setMaxResults(1);
		ScrollableResults sr = null;
		try{
			sr = q.scroll();
			return sr.next();
		}
		finally{
			if (sr!=null){
				sr.close();
			}
		}
	}
	
	private interface ArgumentStackStrategy{
		public Stack getArgumentStack(Object [] arguments);
	}
	
	private static class EmptyStackStrategy implements ArgumentStackStrategy{
		public Stack getArgumentStack(Object[] arguments) {
			return EmptyStack.INSTANCE;
		}
	}

	private static class ArrayListStackStrategy implements ArgumentStackStrategy{
		@SuppressWarnings("unchecked")
		public Stack getArgumentStack(Object[] arguments) {
			final ArrayListStack stack = new ArrayListStack(arguments.length);
			for (int i=arguments.length-1; i>=0; i--){
				stack.push(arguments[i]);
			}
			return stack;
		}
	}
}
